#pragma once

#include <type_traits>
#include <array>
#include <iostream>
#include <functional>

namespace PassValueOrRef
{
    /**
     * 按值传递或按引用传递
     * 1、按值传递：类型衰退
     * 2、按引用传递：不会有类型衰退
     * 3、使用std::ref和std::cref
     * 4、处理字符串字面值和数组
     * 5、处理返回值：返回值通常按值传递
     * 6、推荐的模板参数声明：使用 std::decay<> 和 std::reference_wrapper<> 进行处理，参考 std::make_pair<>
     */

    /**
     * 1、按值传递
     * 1.1.按值传递会有类型衰变
     * 当按值传递参数时，类型会衰变。从而数组将转换为指针，
     * 并删除 const 和 volatile 等限定符 (就像使用值作为使用 auto 声明的对象的初始化式一样)
     *
     * 1.2.按引用传递不会又类型衰变
     * 当通过引用将参数进行传递时，就不会衰变。从而不会将数组转换为指针，
     * 并且不删除 const 和 volatile 等限定符
     * template<typename T>
     * void test(T& val);
     * const std::string s = "hi"; test(s); // T deduced as const std::string&
     */

    /**
     * 2、按引用传递
     */
    // 2.2.传递非引用常量：当通过传递的参数作为返回值时 (例如，使用 out 或 inout 参数时)，必须使用非常量引用
    //      传入outR的可能是一个 T const& 类型，这种类型会导致outR内部对arg的修改失败，
    //      如果想禁用将常量对象传递给非常量引用，可以这样做:
    //          1、使用静态断言触发编译时错误   2、使用 std::enable_if<>(参见章节 6.3) 禁用此模板
    template <typename T, typename = std::enable_if_t<!std::is_const_v<T>>> // 使用enable_if禁用模板
    void outR(T &arg)
    {
        static_assert(!std::is_const_v<T>); // 使用静态断言触发编译时错误
        if (std::is_array_v<T>)
        {
            std::cout << "T is array<T>" << std::endl;
        }
    }

    // 2.3.通过转发引用进行传递
    //      需要注意的地方在于，如果函数像以下方式定义，传入一个 左值引用 会报错，如：
    //      int i; passR(i); // arg 是 int& 类型，而引用类型 x 无法直接初始化
    template <typename T>
    void passR(T &&arg)
    {
        T x;
        // ...
    }

    /**
     * 3、使用 std::ref() 和 std::cref()：返回一个 std::reference_wrapper<>，引用原始参数，并按值传递了这个对象
     *    可以去查看std::reference_wrapper<>的定义，其内部有一个指针，指向传入std::reference_wrapper<>构造函数的输入参数
     *      3.1.这两个函数定义在 <functional> 中
     *      3.2.编译器必须知道返回原始类型必要的隐式转换。因此，只有通过泛型代码传递对象时，
     *          std::ref()和 std::cref() 才能正常工作。
     */

    /*
    _EXPORT_STD template <class _Ty>
    class reference_wrapper
    #if !_HAS_CXX20
        : public _Weak_types<_Ty>
    #endif // !_HAS_CXX20
    {
    public:
    // ...

    using type = _Ty;

    // 构造函数
    template <class _Uty, enable_if_t<conjunction_v<negation<is_same<_Remove_cvref_t<_Uty>, reference_wrapper>>,
                                          _Refwrap_has_ctor_from<_Ty, _Uty>>,
                              int> = 0>
    _CONSTEXPR20 reference_wrapper(_Uty&& _Val) noexcept(   // _Val被设置为通用引用，模板定义中的其他东西如_Remove_cvref_t还不认识
        noexcept(_STD _Refwrap_ctor_fun<_Ty>(_STD declval<_Uty>()))) { // qualified: avoid ADL, handle incomplete types
        _Ty& _Ref = static_cast<_Uty&&>(_Val);
        _Ptr      = _STD addressof(_Ref);   // 指针指向传入的传入的_Val的地址
    }

    // 类型转换操作符，将一个类对象隐式转换为另外一个类型的对象
    _CONSTEXPR20 operator _Ty&() const noexcept {
        return *_Ptr;
    }

    _NODISCARD _CONSTEXPR20 _Ty& get() const noexcept {
        return *_Ptr;
    }

    private:
        _Ty* _Ptr{};  // std::reference_wrapper<> 的成员变量，指针变量

    public:
        // ...
    };
     */
    void printString(const std::string &s)
    {
        std::cout << s << std::endl;
    }

    /*
    std::string s = "hi";
    printT(std::ref(s));
    // 在printT中调用了 printString(arg)，printString(const std::string&)，传入参数为 const std::string&。
    // 而 std::reference_wrapper<> 中的 template<typename _Ty> operator _Ty&(); 成员函数
    // 会将 arg 对象隐式转换为一个 std::string 对象，传入到 printString中，从而实现函数调用
    */
    template <typename T>
    void printT(T arg)
    {
        std::cout << std::is_same_v<T, const char *> << std::endl;
        printString(arg); // printT(std::ref(s)); 调用成功，因为 std::reference_wrapper中定义了隐式转换函数
        // std::cout << arg << std::endl; // printT(std::ref(s)); 调用失败，因为 std::reference_wrapper中没有定义 operator(ostream&,std::const referce_wapper<T>&);
    }

    /**
     * 4、处理字符串字面值和数组
     */

    /**
     * 5、处理返回值：尽量使函数模板按值返回结果，如果按引用返回值可能会出现意想不到的后果（比如对象已经被销毁）
     *  5.1.使用类型特征 std::remove_reference<>(参见 D.4 节) 将类型 T 转换为非引用
     *  5.2.编译器通过声明返回类型 auto 来推断返回类型 (C++14 起; 参见第 1.3.2 节)，因为 auto 总会衰变
     */

    /*
    例如：
    template<typename T>
    T refR(T&& p){
        return T{...};
    }
    int a = 10;
    refR<int&>(a); // T 就是一个int&，无法直接{}创建对象，从而报错
    */

    // 1、使用类型特征 std::remove_reference<>(参见 D.4 节) 将类型 T 转换为非引用
    template <typename T>
    typename std::remove_reference_t<T> retV1(T p)
    {
        return std::remove_reference_t<T>(p);
    }

    // 2、编译器通过声明返回类型 auto 来推断返回类型 (C++14 起; 参见第 1.3.2 节)，因为 auto 总会衰变
    template <typename T>
    auto retV2(T p)
    {
        return T{p};
    }

     /**
     * 6、推荐的模板参数声明
     * • 通过值传递参数:
     *       这种方法很简单，衰变字符串字面值和数组，但不能为大型对象提供最佳性能。
     *       调用者可以决定使用 std::cref() 和 std::ref() 通过引用传递，但是必须确定这样做的必要性。
     * • 通过引用传递参数:
     *       这种方法通常可以为大型对象提供更好的性能，特别是在传递参数时
     *       - 现有对象 (lvalue) 到左值引用，
     *       - 临时对象 (prvalue) 或标记为可移动 (xvalue) 的对象将引用右值，
     *       - 或者两者都为转发引用。
     */
    // 6.1.不要过于泛化：实践中，函数模板通常不支持任意类型的参数，可以进行了一些约束。
    // 比如：知道该函数只会传递某种类型的vector
    template<typename T>
    void printVector(const std::vector<T>& vec){std::enable_if_t}

    // 6.2.std::make_pair() 的实例如下，
    //      C++ 标准库现在在许多地方以类似的方式完美地转发传递的参数，并与 std::decay<> 一起使用 ！！！
    /*
    // before cpp98：
    // 存在问题：比如传入：auot p = make_pair("abc",123);
    //  T1被推导为 char[4] const&。那么修改 p.first = "hello" 时就会出现问题。
    template<typename _T1, typename _T2>
    inline pair<_T1, _T2>
    make_pair(_T1 const& __x, _T2 const& __y)
    { return pair<_T1, _T2>(__x, __y); }

    // before cpp03
    // 存在问题：按值传递，如果传递 std::vector 就会造成性能开销
    template<typename _T1, typename _T2>
    inline pair<_T1, _T2>
    make_pair(_T1 __x, _T2 __y)
    { return pair<_T1, _T2>(__x, __y); }

    // since cpp11
    // 学习样例 × 1
    template<typename _T1, typename _T2>
    constexpr pair<typename __decay_and_strip<_T1>::__type, // 使用decay和reference_wrapper得到衰退后的类型
                   typename __decay_and_strip<_T2>::__type> // 使用转发引用支持完美转发
    make_pair(_T1&& __x, _T2&& __y)
    {
      typedef typename __decay_and_strip<_T1>::__type __ds_type1;
      typedef typename __decay_and_strip<_T2>::__type __ds_type2;
      typedef pair<__ds_type1, __ds_type2> 	      __pair_type;
      return __pair_type(std::forward<_T1>(__x), std::forward<_T2>(__y));
    }

    template<typename _Tp>
    struct __decay_and_strip
    {
      using  __type = typename __strip_reference_wrapper<typename decay<_Tp>::type>::__type;
    };

    // 如果传入的是 std::ref() 函数的类型，则__type是引用类型
    // 否则__type就是普通类型，std::make_pair这里使用的是普通类型
    template<typename _Tp>
    struct __strip_reference_wrapper
    {
      typedef _Tp __type;
    };

    template<typename _Tp>
    struct __strip_reference_wrapper<reference_wrapper<_Tp> >
    {
      typedef _Tp& __type;
    };
     */

};